<template>
	<div class="page-list">
		<!-- 头部块 -->
		<Navbar :isOut="false" bgColor="#212526" theme="dark" />
		<!-- 头部块 -->

		<!-- 主体内容 -->
		<div class="meeting-box" id="meeting-room" v-if="!isMobile">

			<div class="meeting-body">

				<!-- 主内容窗口 -->
				<div class="meeting-section" :style="{ padding: toolbar.isFullScreen ? '0' : '' }">

					<!-- 视频 -->
					<div class="meeting-main" id="meeting-main">
						<div class="mian-video" ref="mainVideo">
							<user-video v-if="mainStream" :user="userList[streamId] || user" :stream-manager="mainStream"
								isMain ref="mainStream" />
						</div>
					</div>
					<div class="sub-list">
						<div v-if="terminalVideo" class="sub-item terminal-video" id="device-video"
							@click="updateMainStream(publisher)">
							<user-video :user="user" :stream-manager="publisher" />
						</div>
						<div v-for="(item, key) in terminalUsers" :key="key" class="sub-item terminal-video"
							@click="updateMainStream(subscribers[key])">
							<user-video v-if="subscribers[key]" :user="item" :infoShow="false"
								:stream-manager="subscribers[key]" />
						</div>
						<div v-for="(item, index) in mainEmpty" :key="index" class="sub-item"></div>
					</div>
					<!-- 视频 -->

					<!-- 会议工具按钮组 -->
					<toolbar ref="toolbar" :meetingRoomName="meetingRoomName" :OV="OV" :session="session"
						:publisher="publisher">
					</toolbar>
					<!-- 会议工具按钮组 -->

				</div>
				<!-- 主内容窗口 -->
			</div>

			<!-- 人员信息侧边栏 -->
			<div class="meeting-side" :style="{ height: toolbar.isFullScreen ? '100vh' : '' }">
				<div class="sub-list" style="border-bottom: 1px solid;">
					<div v-if="isNormalUser" class="sub-item">
						<user-video v-if="publisher" :user="user" :stream-manager="publisher" />
					</div>
					<div v-for="(item, key) in normalUsers" class="sub-item" :key="key">
						<user-video v-if="subscribers[key]" :user="item" :stream-manager="subscribers[key]" />
					</div>
					<div v-for="(item, key) in userEmpty" class="sub-item" :key="key">
						<div class="empty-box">未接入</div>
					</div>
				</div>
				<Chat ref="chat" :session="session" :user="user" :meetingRoomCode="meetingRoomCode" />
			</div>
			<!-- 人员信息侧边栏 -->

		</div>
		<!-- 主体内容 -->

		<!-- 移动端视图 -->
		<div class="mobile-room" v-if="isMobile">
			<div class="mobile-main">
				<user-video v-if="mainStream" :user="userList[streamId] || user" :stream-manager="mainStream" isMain
					ref="mainStream" />
			</div>
			<div class="sub-list">
				<div v-if="publisher" class="sub-item" @click="updateMainStream(publisher)">
					<user-video :user="user" :stream-manager="publisher" />
				</div>
				<div v-for="(item, key) in userList" :key="key" class="sub-item"
					@click="updateMainStream(subscribers[key])">
					<user-video v-if="subscribers[key]" :user="item" :stream-manager="subscribers[key]" />
				</div>
			</div>
			<Chat ref="chat" :session="session" :user="user" :meetingRoomCode="meetingRoomCode"></Chat>
			<Toolbar ref="toolbar" :meetingRoomName="meetingRoomName" :OV="OV" :session="session" :publisher="publisher">
			</Toolbar>
		</div>

		<el-dialog title="提示" :visible.sync="joinShow" :close-on-click-modal="false" :close-on-press-escape="false"
			:show-close="false" :append-to-body="false" width="30%" center>
			<span>正在等待加入申请同意中...</span>
			<span slot="footer" class="dialog-footer">
				<el-button @click="$router.go(-1)">取消等待</el-button>
			</span>
		</el-dialog>

		<Network></Network>

	</div>
</template>

<script>
import {
	OpenVidu
} from "openvidu-browser";
import {
	joinMeetingRoom,
	uploadCover
} from '@/api'
import {
	createScreenshot
} from "@/utils/tools"
import {
	mapState
} from 'vuex'
import UserVideo from "@/components/UserVideo";
import Chat from "@/components/Chat";
import Navbar from '@/components/Navbar.vue'
import Toolbar from '@/components/Toolbar'
import Network from '@/components/Network.vue'
export default {
	name: 'Room',
	components: {
		UserVideo,
		Navbar,
		Chat,
		Toolbar,
		Network
	},
	data() {
		return {
			meetingRoomCode: '', // 会议室编码
			meetingRoomName: '', // 会议名称
			connectionToken: '', // 会议室token,
			launchId: '', // 会议发起人

			OV: undefined, // openvidu对象
			session: undefined, // openvidu会话
			publisher: undefined, // 本地流
			mainStream: undefined, // 主窗口流
			streamId: undefined, // 主窗口ID

			subscribers: {}, // 会议人员订阅流列表
			userList: {}, // 会议人员列表
			toolbar: {},
			hasValidAudio: true,
			videoId: '',
			joinShow: false,
			coverTimer: null,
			sessionId: null
		}
	},
	computed: {
		...mapState(['user', 'websock']),
		isTerminalUser() {
			return this.user.userType === '0'
		},
		isNormalUser() {
			return this.user.userType === '1'
		},
		terminalUsers() {
			let target = this.getUsers('0')
			this.$delete(target, this.streamId)
			return target
		},
		normalUsers() {
			return this.getUsers('1')
		},
		terminalVideo() {
			return this.isTerminalUser && this.publisher
		},
		mainEmpty() {
			let len = Object.keys(this.terminalUsers).length
			return 1 - len < 0 ? 0 : 1 - len
		},
		userEmpty() {
			let count = this.isTerminalUser ? 6 : 5
			return count - Object.keys(this.normalUsers).length
		}
	},
	created() {
		let query = this.$route.query
		this.meetingRoomCode = query.code // 接收界面传递参数
		this.sessionId = query.sessionId
	},
	beforeRouteLeave(to, from, next) {
		this.leaveSession()
		next()
	},
	async mounted() {

		this.OV = new OpenVidu(); // 获取RTC操作对象
		this.session = this.OV.initSession(); // 初始化会话对象
		this.setCoturn() // coturn参数配置

		await this.OV.getDevices().then(devices => {
			let videoDevices = devices.filter(device => device.kind === "videoinput");
			let audioDevices = devices.filter(device => device.kind === "audioinput");
			if (videoDevices.length) {
				this.videoId = videoDevices[0].deviceId
			}
			if (!audioDevices.length) {
				this.hasValidAudio = false
			}
		})

		this.session.on("streamCreated", this.onStreamCreated); // 监听人员加入
		this.session.on("streamDestroyed", this.onStreamDestroyed); // 监听人员离开
		this.session.on("exception", this.onException); // 监听异常情况
		this.session.on("signal:chat", this.$refs.chat.getSignal); // 接收会议消息
		this.session.on('signal:confirm', this.onJoinConfirm); // 接收会议消息
		this.session.on('signal:join', this.onJoinByMessage); // 接收会议消息
		this.onJoinRoom() // 加入房间
		this.toolbar = this.$refs.toolbar

	},
	methods: {
		// 根据类型获取用户列表
		getUsers(type) {
			let target = {}
			let users = this.userList
			for (let i in users) {
				if (users[i].userType === type) {
					target[i] = users[i]
				}
			}
			return target
		},
		// 加入会议室
		onJoinRoom() {
			joinMeetingRoom({
				meetingRoomCode: this.meetingRoomCode,
				sessionId: this.sessionId || null
			}).then(res => {
				this.connectionToken = res.data.connectionToken
				this.launchId = res.data.userId
				this.meetingRoomName = res.data.meetingRoomName
				this.connectSession()
			}).catch(err => {
				this.$message.error(err.msg || '创建会议室失败！')
			})
		},
		// 人员进入处理事件
		onStreamCreated(e) {
			console.log('监听人员进入', e);
			const user = this.getUser(e.stream)
			const streamId = e.stream.streamId
			console.log(this.subscribers);
			console.log(this.userList);
			if (streamId in this.subscribers) {
				this.$set(this.subscribers, streamId, undefined) // 加入成员
				this.$set(this.userList, streamId, undefined)
			}
			const subscriber = this.session.subscribe(e.stream);
			this.$set(this.subscribers, streamId, subscriber) // 加入成员
			this.$set(this.userList, streamId, user) // 加入成员
			if (user.sessionModerator && !user.loginName.includes('_01')) {
				this.streamId = streamId
				this.mainStream = undefined
				this.$nextTick(() => {
					this.mainStream = subscriber
				})
			}
		},
		getUser(stream) {
			let data = stream.connection.data || stream.session.connection.data
			return JSON.parse(data.split('%')[0])
		},
		// 人员离开处理事件
		onStreamDestroyed(e) {
			const streamId = e.stream.streamId
			this.$delete(this.subscribers, streamId);
			this.$delete(this.userList, streamId);
			if (this.streamId == streamId) {
				this.streamId = undefined
				this.mainStream = undefined
			}
		},
		// 异常情况处理事件
		onException(e) {
			console.warn(e);
		},
		// 开始连接
		connectSession() {
			this.session.connect(this.connectionToken, this.user)
				.then(res => {
					if (this.user.connectionType != 'IPCAM') {
						this.onInitPublisher()
					} else {
						this.sendUserOnline()
					}
					window.addEventListener("beforeunload", this.leaveSession); // 监听页面卸载 关闭会话
				})
				.catch((error) => {
					console.log("会话连接错误", error.code, error.message);
				});
		},
		sendUserOnline() {
			setTimeout(() => {
				const data = {
					"event": "joinMeetingRoom",
					"meetingRoomCode": this.meetingRoomCode,
					"sessionId": this.sessionId,
					"userId": this.user.id
				}
				this.websock.send(JSON.stringify(data))
			}, 1000);
		},
		// 初始化发布流
		onInitPublisher() {
			// 构建本地流
			this.OV.initPublisherAsync(undefined, this.getPublisherConfig()).then(publisher => {
				this.publisher = publisher; // 保存本地流为当前直播流
				this.session.publish(this.publisher); // 开始发布本地流
				this.sendUserOnline()
				if (this.user.sessionModerator && this.isTerminalUser) {
					this.getMeetingCover()
					this.session.on('signal:action', this.$refs.mainStream.getSignal); // 接收用户云台操作消息
				}
			})
		},
		// 获取音视频流配置
		getPublisherConfig(invite = false) {
			let publishVideo = this.getPublishVideo()
			let target = {
				publishAudio: this.getPublishAudio(), // 是否发布音频
				publishVideo, // 是否发布视频
				frameRate: 30, // 视频帧率
				resolution: '1920x1080',
				audioSource: this.hasValidAudio,
				[this.videoId && publishVideo ? 'videoSource' : 'vs']: this.videoId,
				videoCodec: 'H264'
			}
			return target
		},
		// 获取是否能发布音频
		getPublishAudio() {
			let publishAudio = true
			if (this.user.loginName.includes('_1')) {
				publishAudio = false
			}
			if (!this.hasValidAudio) {
				publishAudio = false
			}
			this.toolbar.publishAudio = publishAudio
			return publishAudio
		},
		// 获取是否能发布视频
		getPublishVideo() {
			let publishVideo = true
			this.toolbar.publishVideo = publishVideo
			return publishVideo
		},
		isExist(userId) {
			let users = this.userList
			return Object.keys(users)
				.some(key => users[key].id == userId)
		},
		// 询问是否加入事件
		async onJoinConfirm(e) {
			console.log('询问是否能够进入', e);
			const user = JSON.parse(e.data)
			if (this.launchId == this.user.id) {
				let join = true
				try {
					await this.$confirm(`是否同意${user.userName}（${user.userDept}）进入会议？`, '提示', {
						confirmButtonText: '同意',
						cancelButtonText: '不同意',
						showCancelButton: true,
						type: 'warning'
					})
				} catch (err) {
					join = false
				}
				this.session.signal({
					data: JSON.stringify({
						join,
						userId: user.id
					}), // 消息数据
					type: 'join' // 消息类型
				})
			}
		},
		// 是否同意加入会议
		onJoinByMessage(e) {
			console.log('是否同意加入会议', e);
			const msg = JSON.parse(e.data)
			if (msg.userId == this.user.id) {
				if (msg.join) {
					this.$message.info('会议发起人已同意申请，即将进入会议！')
					this.session.unpublish(this.publisher);
					let publisher = this.OV.initPublisher(undefined, this.getPublisherConfig(true));
					this.toggleStream(() => {
						this.session.publish(this.publisher);
					}, publisher)
					this.joinShow = false
				} else {
					this.$message.error('会议发起人拒绝你加入会议！')
					this.$router.go(-1)
				}
			}
		},
		/**
		 * @name 离开会话
		 * @param {Boolean} bool 是否需要返回界面
		 */
		leaveSession() {
			try {
				this.session && this.session.disconnect()
				const data = {
					"event": "leaveMeetingRoom",
					"meetingRoomCode": this.meetingRoomCode,
					"userId": this.user.id
				}
				this.websock.send(JSON.stringify(data))
				this.coverTimer && clearInterval(this.coverTimer)

				this.session = undefined;
				this.mainStream = undefined;
				this.publisher = undefined;
				this.subscribers = {};
				this.userList = {};
				this.OV = undefined;
				window.removeEventListener("beforeunload", this.leaveSession); // 移除页面卸载监听
			} catch (error) {

			}
		},
		// 更新主窗口视频流
		updateMainStream(stream) {
			if (this.mainStream === stream) return;
			const {
				streamId
			} = stream.stream
			this.streamId = streamId
			this.mainStream = null
			this.$nextTick(() => this.mainStream = stream)
		},
		toggleStream(callback, publisher) {
			let isMainStream = this.mainStream === this.publisher
			if (isMainStream) this.mainStream = null
			this.publisher = null
			this.$nextTick(() => {
				if (isMainStream) this.mainStream = publisher
				this.publisher = publisher
				callback()
			})
		},
		// openvidu连接配置
		setCoturn() {
			let turnUsername = "openvidu_cn";
			let turnCredential = "openvidu_cn";
			this.OV.setAdvancedConfiguration({
				iceServers: [{
					urls: "turn:47.99.133.183:3478?transport=udp",
					username: turnUsername,
					credential: turnCredential
				}]
			});
		},
		dataURLtoBlob(dataurl) {
			var arr = dataurl.split(','),
				mime = arr[0].match(/:(.*?);/)[1],
				bstr = atob(arr[1]),
				n = bstr.length,
				u8arr = new Uint8Array(n);
			while (n--) {
				u8arr[n] = bstr.charCodeAt(n);
			}
			return new Blob([u8arr], {
				type: mime
			});
		},
		//2,再将blob转换为file
		blobToFile(theBlob, fileName) {
			theBlob.lastModifiedDate = new Date(); // 文件最后的修改日期
			theBlob.name = fileName; // 文件名
			return new File([theBlob], fileName, {
				type: theBlob.type,
				lastModified: Date.now()
			});
		},
		// 获取会议室封面
		getMeetingCover() {
			this.coverTimer = setInterval(() => {
				let canvasBox = document.getElementById('device-video')
				createScreenshot(canvasBox, false).then(filePath => {
					const blob = this.dataURLtoBlob(filePath)
					const file = this.blobToFile(blob, 'meetingCover')
					let formdata = new FormData()
					formdata.append('multipartFile', file)
					formdata.append('meetingRoomCode', this.meetingRoomCode)
					formdata.append('sessionId', this.sessionId)

					uploadCover(formdata).then(res => {
						console.log('会议室封面上传', res);
					})
				})
			}, 1000 * 60 * 10)
		}
	}
}
</script>
<style lang="scss" scoped>
.box-bg {
	background: #FFFFFF;
	box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.1);
	border-radius: 4px 4px 4px 4px;
}

.page-list {
	width: 100%;
	height: 100vh;
	box-sizing: border-box;
	background: #F8F8F8;
	display: flex;
	flex-direction: column;

	.meeting-box {
		flex: 1;
		display: flex;
		width: 100%;
	}

	.meeting-body {
		flex: 1;
		height: 100%;
		display: flex;
		flex-direction: column;

		.meeting-section {
			@extend .box-bg;
			flex: 1;
		}
	}

	.meeting-section {
		display: flex;
		flex-direction: column;

		.meeting-main {
			flex: 1;
		}

	}

}

.meeting-main {
	display: flex;

	.mian-video {
		position: relative;
		background-color: #000000de;
		flex: 1;
		height: 100%;

		::v-deep video {
			object-fit: cover;
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%
		}
	}
}

.meeting-side {
	width: 30vw;
	height: 100%;
	display: flex;
	flex-direction: column;
}

.sub-list {
	width: 100%;
	background-color: #272727;
	display: flex;
	flex-wrap: wrap;
	align-content: flex-start;
	overflow-x: auto;

	.sub-item {
		width: calc(100% / 3) !important;
		height: 150px;
		position: relative;
		border: 1px solid #848484;

		.empty-box {
			width: 100%;
			height: 100%;
			display: flex;
			align-items: center;
			justify-content: center;
			color: #fff;
		}

		::v-deep video {
			object-fit: cover;
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%
		}
	}
}

.mobile-room {
	flex: 1;
	height: 0;
	background-color: #606266;
	display: flex;
	flex-direction: column;

	.mobile-main {
		flex: 2;
		height: 0;

		::v-deep video {
			width: 100%;
		}
	}

	.message-container {
		flex: 1;
		height: 0;
	}

	.sub-list {
		flex: 1;
		height: 0;
	}
}

.terminal-video {
	max-width: 300px;
}
</style>